home *** CD-ROM | disk | FTP | other *** search
Wrap
#!/usr/bin/env python # -*- coding: utf-8 -*- # # (c) Copyright 2003-2007 Hewlett-Packard Development Company, L.P. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # Author: Don Welch # from __future__ import division __version__ = '1.0' __title__ = 'Scan Utility' __doc__ = "SANE-based scan utility for HPLIP." # Std Lib import sys import os import os.path import getopt import signal import time import socket # Local from base.g import * from base import tui import base.utils as utils from base import device from prnt import cups log.set_module('hp-scan') prop.prog = sys.argv[0] device_uri = None username = prop.username mode = GUI_MODE mode_specified = False res = 300 scan_mode = 'color' tlx = None tly = None brx = None bry = None units = "mm" output = '' dest = [] email_from = '' email_to = [] email_subject = 'hp-scan from %s' % socket.gethostname() email_note = '' fax = '' resize = 100 contrast = 0 brightness = 0 printer = '' page_size = '' size_desc = '' page_units = 'mm' valid_res = (75, 150, 300, 600, 1200, 2400, 4800) default_res = 300 scanner_compression = 'JPEG' adf = False PAGE_SIZES = { # in mm '5x7' : (127, 178, "5x7 photo", 'in'), '4x6' : (102, 152, "4x6 photo", 'in'), '3x5' : (76, 127, "3x5 index card", 'in'), 'a2_env' : (111, 146, "A2 Envelope", 'in'), 'a3' : (297, 420, "A3", 'mm'), "a4" : (210, 297, "A4", 'mm'), "a5" : (148, 210, "A5", 'mm'), "a6" : (105, 148, "A6", 'mm'), "b4" : (257, 364, "B4", 'mm'), "b5" : (182, 257, "B5", 'mm'), "c6_env" : (114, 162, "C6 Envelope", 'in'), "dl_env" : (110, 220, "DL Envelope", 'in'), "exec" : (184, 267, "Executive", 'in'), "flsa" : (216, 330, "Flsa", 'mm'), "higaki" : (100, 148, "Hagaki", 'mm'), "japan_env_3" : (120, 235, "Japanese Envelope #3", 'mm'), "japan_env_4" : (90, 205, "Japanese Envelope #4", 'mm'), "legal" : (215, 356, "Legal", 'in'), "letter" : (215, 279, "Letter", 'in'), "no_10_env" : (105, 241, "Number 10 Envelope", 'in'), "oufufu-hagaki" : (148, 200, "Oufuku-Hagaki", 'mm'), "photo" : (102, 152, "Photo", 'in'), "super_b" : (330, 483, "Super B", 'in'), } USAGE = [(__doc__, "", "name", True), ("Usage: hp-scan [SANE-DEVICE-URI] [MODE] [-n OPTIONS] [OPTIONS]", "", "summary", True), ("[SANE-DEVICE-URI]", "", "header", False), ("SANE device URI:", "-d<sane_device_uri> or --device=<sane_device_uri>", "option", False), ("", "URI format: hpaio:/<bus>/<model>?<identification>", "option", False), utils.USAGE_SPACE, ("[MODE]", "", "header", False), ("Enter graphical UI mode:", "-u or --gui (Default)", "option", False), ("Run in non-interactive mode:", "-n or --non-interactive", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] (General) (Not applicable to GUI mode)", "", "header", False), ("Scan destinations:", "-s<dest_list> or --dest=<dest_list>", "option", False), ("", "where <dest_list> is a comma separated list containing one or more of: 'file'\*, ", "option", False), ("", "'viewer', 'editor', 'pdf', 'fax', or 'print'. Use only commas between values, no spaces.", "option", False), ("Scan mode:", "-m<mode> or --mode=<mode>. Where <mode> is 'color'\*, 'gray' or 'lineart'.", "option", False), ("Scanning resolution:", "-r<resolution_in_dpi> or --res=<resolution_in_dpi> or --resolution=<resolution_in_dpi>", "option", False), ("", "where <resolution_in_dpi> is %s (300 is default)." % ', '.join([str(x) for x in valid_res]), "option", False), ("Image resize:", "--resize=<scale_in_%> (min=1%, max=400%, default=100%)", "option", False), ("Image contrast:", "--contrast=<contrast>", "option", False), ("ADF mode (EXPERIMENTAL):", "--adf (Note, only PDF output is supported when using the ADF)", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] (Scan area) (Not applicable to GUI mode)", "", "header", False), ("Specify the units for area/box measurements:", "-t<units> or --units=<units>", "option", False), ("", "where <units> is 'mm'\*, 'cm', 'in', 'px', or 'pt' ('mm' is default).", "option", False), ("Scan area:", "-a<tlx>,<tly>,<brx>,<bry> or --area=<tlx>,<tly>,<brx>,<bry>", "option", False), ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units for tlx, tly, brx, and bry are specified by -t/--units (default is 'mm').", "option", False), ("", "Use only commas between values, no spaces.", "option", False), ("Scan box:", "--box=<tlx>,<tly>,<width>,<height>", "option", False), ("", "tlx and tly coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units for tlx, tly, width, and height are specified by -t/--units (default is 'mm').", "option", False), ("", "Use only commas between values, no spaces.", "option", False), ("Top left x of the scan area:", "--tlx=<tlx>", "option", False), ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units are specified by -t/--units (default is 'mm').", "option", False), ("Top left y of the scan area:", "--tly=<tly>", "option", False), ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units are specified by -t/--units (default is 'mm').", "option", False), ("Bottom right x of the scan area:", "--brx=<brx>", "option", False), ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units are specified by -t/--units (default is 'mm').", "option", False), ("Bottom right y of the scan area:", "--bry=<bry>", "option", False), ("", "Coordinates are relative to the upper left corner of the scan area.", "option", False), ("", "Units are specified by -t/--units (default is 'mm').", "option", False), ("Specify the scan area based on a paper size:", "--size=<paper size name>", "option", False), ("", "where <paper size name> is one of: %s" % ', '.join(PAGE_SIZES.keys()), "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('file' dest) (Not applicable to GUI mode)", "", "header", False), ("Filename for 'file' destination:", "-o<file> or -f<file> or --file=<file> or --output=<file>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('pdf' dest) (Not applicable to GUI mode)", "", "header", False), ("PDF viewer application:", "--pdf=<pdf_viewer>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('viewer' dest) (Not applicable to GUI mode)", "", "header", False), ("Image viewer application:", "-v<viewer> or --viewer=<viewer>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('editor' dest) (Not applicable to GUI mode)", "", "header", False), ("Image editor application:", "-e<editor> or --editor=<editor>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('email' dest) (Not applicable to GUI mode)", "", "header", False), ("From: address for 'email' dest:", "--email-from=<email_from_address> (required for 'email' dest.)", "option", False), ("To: address for 'email' dest:", "--email-to=<email__to_address> (required for 'email' dest.)", "option", False), ("Email subject for 'email' dest:", '--email-subject="<subject>"', "option", False), ("", 'Use double quotes (") around the subject if it contains space characters.', "option", False), ("Note or message for the 'email' dest:", '--email-msg="<msg>" or --email-note="<note>"', "option", False), ("", 'Use double quotes (") around the note/message if it contains space characters.', "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('fax' dest) (Not applicable to GUI mode)", "", "header", False), ("Fax queue/printer:", "--fax=<fax_printer_name>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] ('printer' dest) (Not applicable to GUI mode)", "", "header", False), ("Printer queue/printer:", "--printer=<printer_name>", "option", False), utils.USAGE_SPACE, ("[-n OPTIONS] (advanced) (Not applicable to GUI mode)", "", "header", False), ("Set the scanner compression mode:", "-x<mode> or --compression=<mode>, <mode>='raw', 'none' or 'jpeg' ('jpeg' is default) ('raw' and 'none' are equivalent)", "option", False), utils.USAGE_SPACE, utils.USAGE_OPTIONS, utils.USAGE_LOGGING1, utils.USAGE_LOGGING2, utils.USAGE_LOGGING3, utils.USAGE_HELP, utils.USAGE_NOTES, ("1. If no dest is provided, the 'file' dest will be automatically invoked.", "", "note", False), ("2. If applications for viewer, editor, or pdf viewer are not provided, reasonable defaults will be used.", "", "note", False), ("3. If --printer is not specified, the CUPS default will be used if available.", "", "note", False), ("4. If an output file is not specified with the 'file' dest, a reasonable default will be used.", "", "note", False), ("5. Some options may not be valid on some scanning devices.", "", "note", False), ("6. The following features are not yet implemented: GUI mode, batch scanning, film/negative scanning, contrast adjustment, brightness adjustment, autocrop, resize to axb, resize to xKB, ", "", "note", False), utils.USAGE_SPACE, utils.USAGE_EXAMPLES, ("Quickly (low-res) scan entire page in color to file:", "$ hp-scan -n -r75", "example", False), ("Scan upper left 1in corner and send as email:", '$ hp-scan -n --box=0,0,1,1 -tin -semail --email-from=foo@bar.org --email-to=bar@foo.org --email-note="Test scan" --email-subject="Test scan email"', "example", False), ("Scan entire page in 300dpi grayscale and then edit:", "$ hp-scan -n -seditor -mgray", "example", False), ("Launch the hp-scan GUI:", "$ hp-scan", "example", False), ("Scan into The GIMP:", "$ hp-scan -n --editor=gimp", "example", False), ] def usage(typ='text'): if typ == 'text': utils.log_title(__title__, __version__) utils.format_text(USAGE, typ, __title__, 'hp-scan', __version__) sys.exit(0) try: viewer = '' viewer_list = ['kview', 'display', 'gwenview', 'eog', 'kuickshow',] for v in viewer_list: vv = utils.which(v) if vv: viewer = os.path.join(vv, v) break editor = '' editor_list = ['kolourpaint', 'gimp', 'krita', 'cinepaint', 'mirage',] for e in editor_list: ee = utils.which(e) if ee: editor = os.path.join(ee, e) break pdf_viewer = '' pdf_viewer_list = ['kpdf', 'acroread', 'xpdf', 'evince',] for v in pdf_viewer_list: vv = utils.which(v) if vv: pdf_viewer = os.path.join(vv, v) break try: opts, args = getopt.getopt(sys.argv[1:],'l:hd:p:b:gunr:m:t:o:s:f:v:e:c:a:x:', ['device=', 'printer=', 'level=', 'help', 'help-rest', 'help-man', 'logfile=', 'gui', 'non-interactive', 'logging=', 'help-desc', 'resolution=', 'res=', 'mode=', 'tlx=', 'tly=', 'brx=', 'bry=', 'units=', 'area=', 'box=', 'output=', 'dest=', 'destination=', 'file=', 'pdf=', 'viewer=', 'editor=', 'email-from=', 'email-to=', 'resize=', 'email-subject=', 'email-note=', 'email-msg=', 'contrast=', 'brightness=', 'size=', 'compression=', 'adf', 'fax=']) except getopt.GetoptError, e: log.error(e.msg) usage() sys.exit(1) if os.getenv("HPLIP_DEBUG"): log.set_level('debug') for o, a in opts: if o in ('-l', '--logging'): log_level = a.lower().strip() if not log.set_level(log_level): usage() elif o == '-g': log.set_level('debug') elif o in ('-h', '--help'): usage() elif o == '--help-rest': usage('rest') elif o == '--help-man': usage('man') elif o == '--help-desc': print __doc__, sys.exit(0) elif o in ('-d', '--device'): device_uri = a elif o in ('-n', '--non-interactive'): if mode_specified: log.error("You may only specify a single mode as a parameter (-n or -u).") sys.exit(1) mode = NON_INTERACTIVE_MODE mode_specified = True elif o in ('-u', '--gui'): if mode_specified: log.error("You may only specify a single mode as a parameter (-n or -u).") sys.exit(1) mode = GUI_MODE mode_specified = True elif o in ('-x', '--compression'): a = a.strip().lower() if a in ('jpeg', 'jpg'): scanner_compression = 'JPEG' elif a in ('raw', 'none'): scanner_compression = 'None' else: log.error("Invalid compression value. Valid values are 'jpeg', 'raw', and 'none'.") log.error("Using default value of 'jpeg'.") scanner_compression = 'JPEG' elif o in ('-m', '--mode'): a = a.strip().lower() if a in ('color', 'colour'): scan_mode = 'color' elif a in ('lineart', 'bw', 'b&w'): scan_mode = 'lineart' elif a in ('gray', 'grayscale', 'grey', 'greyscale'): scan_mode = 'gray' else: log.error("Invalid mode. Using default of 'color'.") log.error("Valid modes are 'color', 'lineart', or 'gray'.") scan_mode = 'color' elif o in ('--res', '--resolution', '-r'): try: r = int(a.strip()) except ValueError: log.error("Invalid resolution. Using default of %s dpi." % default_res) log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res])) res = default_res else: if r in valid_res: res = r else: res = valid_res[0] min_dist = sys.maxint for x in valid_res: if abs(r-x) < min_dist: min_dist = abs(r-x) res = x log.warn("Invalid resolution. Using closest valid resolution of %d dpi" % res) log.error("Valid resolutions are %s dpi." % ', '.join([str(x) for x in valid_res])) elif o in ('-t', '--units'): a = a.strip().lower() if a in ('in', 'inch', 'inches'): units = 'in' elif a in ('mm', 'milimeter', 'milimeters', 'millimetre', 'millimetres'): units = 'mm' elif a in ('cm', 'centimeter', 'centimeters', 'centimetre', 'centimetres'): units = 'cm' elif a in ('px', 'pixel', 'pixels', 'pel', 'pels'): units = 'px' elif a in ('pt', 'point', 'points'): units = 'pt' else: log.error("Invalid units. Using default of mm.") units = 'mm' elif o == '--tlx': a = a.strip().lower() try: f = float(a) except ValueError: log.error("Invalid value for tlx.") else: tlx = f elif o == '--tly': a = a.strip().lower() try: f = float(a) except ValueError: log.error("Invalid value for tly.") else: tly = f elif o == '--brx': a = a.strip().lower() try: f = float(a) except ValueError: log.error("Invalid value for brx.") else: brx = f elif o == '--bry': a = a.strip().lower() try: f = float(a) except ValueError: log.error("Invalid value for bry.") else: bry = f elif o in ('-a', '--area'): # tlx, tly, brx, bry a = a.strip().lower() try: tlx, tly, brx, bry = a.split(',')[:4] except ValueError: log.error("Invalid scan area. Using defaults.") else: try: tlx = float(tlx) except ValueError: log.error("Invalid value for tlx. Using defaults.") tlx = None try: tly = float(tly) except ValueError: log.error("Invalid value for tly. Using defaults.") tly = None try: brx = float(brx) except ValueError: log.error("Invalid value for brx. Using defaults.") brx = None try: bry = float(bry) except ValueError: log.error("Invalid value for bry. Using defaults.") bry = None elif o == '--box': # tlx, tly, w, h a = a.strip().lower() try: tlx, tly, width, height = a.split(',')[:4] except ValueError: log.error("Invalid scan area. Using defaults.") else: try: tlx = float(tlx) except ValueError: log.error("Invalid value for tlx. Using defaults.") tlx = None try: tly = float(tly) except ValueError: log.error("Invalid value for tly. Using defaults.") tly = None if tlx is not None: try: brx = float(width) + tlx except ValueError: log.error("Invalid value for width. Using defaults.") brx = None else: log.error("Cannot calculate brx since tlx is invalid. Using defaults.") brx = None if tly is not None: try: bry = float(height) + tly except ValueError: log.error("Invalid value for height. Using defaults.") bry = None else: log.error("Cannot calculate bry since tly is invalid. Using defaults.") bry = None elif o == '--size': size = a.strip().lower() if size in PAGE_SIZES: brx, bry, size_desc, page_units = PAGE_SIZES[size] tlx, tly = 0, 0 page_size = size else: log.error("Invalid page size. Valid page sizes are: %s" % ', '.join(PAGE_SIZES.keys())) log.error("Using defaults.") elif o in ('-o', '--output', '-f', '--file'): output = os.path.abspath(os.path.normpath(os.path.expanduser(a.strip()))) try: ext = os.path.splitext(output)[1] except IndexError: log.error("Invalid filename extension.") output = '' if 'file' in dest: dest.remove('file') else: if ext.lower() not in ('.jpg', '.png'): log.error("Only JPG (.jpg) and PNG (.png) output files are supported.") output = '' if 'file' in dest: dest.remove('file') else: if os.path.exists(output): log.warn("Output file '%s' exists. File will be overwritten." % output) if 'file' not in dest: dest.append('file') elif o in ('-s', '--dest', '--destination'): a = a.strip().lower().split(',') for aa in a: aa = aa.strip() if aa in ('file', 'fax', 'viewer', 'editor', 'printer', 'print', 'email', 'pdf') \ and aa not in dest: if aa == 'print': aa = 'printer' dest.append(aa) elif o in ('-v', '--viewer'): a = a.strip() b = utils.which(a) if not b: log.error("Viewer application not found.") else: viewer = os.path.join(b, a) if 'viewer' not in dest: dest.append('viewer') elif o in ('-e', '--editor'): a = a.strip() b = utils.which(a) if not b: log.error("Editor application not found.") else: editor = os.path.join(b, a) if 'editor' not in dest: dest.append('editor') elif o == '--pdf': a = a.strip() b = utils.which(a) if not b: log.error("PDF viewer application not found.") else: pdf_viewer = os.path.join(b, a) if 'pdf' not in dest: dest.append('pdf') elif o in ('-p', '--printer'): if a.startswith('*'): printer_name = cups.getDefaultPrinter() log.debug(printer_name) if printer_name is not None: log.info("Using CUPS default printer: %s" % printer_name) else: log.error("CUPS default printer is not set.") else: printer_name = a if printer_name is not None: pp = printer_name.strip() printer_list = cups.getPrinters() found = False for p in printer_list: if p.name == pp: found = True printer = pp break if found: if 'printer' not in dest: dest.append('printer') else: log.error("Unknown/invalid printer name: %s" % pp) elif o == '--fax': #print "fax" pp = a.strip() from prnt import cups printer_list = cups.getPrinters() found = False for p in printer_list: if p.name == pp: found = True fax = pp break if found: if 'fax' not in dest: dest.append('fax') else: log.error("Unknown/invalid fax name: %s" % pp) elif o == '--email-to': email_to = a.split(',') if 'email' not in dest: dest.append('email') elif o == '--email-from': email_from = a if 'email' not in dest: dest.append('email') elif o == '--email-subject': email_subject = a if 'email' not in dest: dest.append('email') elif o in ('--email-note', '--email-msg'): email_note = a if 'email' not in dest: dest.append('email') elif o == '--resize': a = a.replace("%", "") try: resize = int(a) except ValueError: resize = 100 log.error("Invalid resize value. Using default of 100%.") elif o in ('-b', '--brightness'): pass elif o in ('-c', '--contrast'): try: contrast = int(a.strip()) except ValueError: log.error("Invalid contrast value. Using default of 100.") contrast = 100 elif o == '--adf': adf = True output_type = 'pdf' utils.log_title(__title__, __version__) if os.getuid() == 0: log.warn("hp-scan should not be run as root.") if 'printer' in dest and not printer: from prnt import cups printer = cups.getDefaultPrinter() if printer is not None: log.warn("Print destination enabled with no printer specified.") log.warn("Using CUPS default printer '%s'." % printer) else: log.error("Print destination enabled with no printer specified.") log.error("No CUPS default printer found. Disabling 'print' destination.") dest.remove("printer") if 'fax' in dest and 'file' not in dest: log.error("Fax destination not implemented. Adding 'file' destination. Use output file to fax.") dest.append('file') if not dest: log.warn("No destinations specified. Adding 'file' destination by default.") dest.append('file') if 'file' in dest and not output: log.warn("File destination enabled with no output file specified.") if adf: log.info("Setting output format to PDF for ADF mode.") output = utils.createSequencedFilename("hpscan", ".pdf") output_type = 'pdf' else: if scan_mode == 'gray': log.info("Setting output format to PNG for greyscale mode.") output = utils.createSequencedFilename("hpscan", ".png") output_type = 'png' else: log.info("Setting output format to JPEG for color/lineart mode.") output = utils.createSequencedFilename("hpscan", ".jpg") output_type = 'jpeg' log.warn("Defaulting to '%s'." % output) else: try: output_type = os.path.splitext(output)[1].lower()[1:] if output_type == 'jpg': output_type = 'jpeg' except IndexError: output_type = '' if output_type and output_type not in ('jpeg', 'png', 'pdf'): log.error("Invalid output file format. File formats must be 'jpeg', 'png', or 'pdf'.") sys.exit(1) #sys.exit(0) ##if scan_mode == 'gray' and output_type and output_type != 'png': ## log.error("Grayscale scans must be saved in PNG file format. To save in other formats, set the 'editor' destination and save the image from the editor in the desired format.") ## sys.exit(1) if adf and output_type and output_type != 'pdf': log.error("ADF scans must be saved in PDF file format.") sys.exit(1) if 'email' in dest and (not email_from or not email_to): log.error("Email specified, but email to and/or email from address(es) were not specified.") log.error("Disabling 'email' destination.") dest.remove("email") if page_size: units = 'mm' if units == 'in': if tlx is not None: tlx = tlx * 25.4 if tly is not None: tly = tly * 25.4 if brx is not None: brx = brx * 25.4 if bry is not None: bry = bry * 25.4 elif units == 'cm': if tlx is not None: tlx = tlx * 10.0 if tly is not None: tly = tly * 10.0 if brx is not None: brx = brx * 10.0 if bry is not None: bry = bry * 10.0 elif units == 'pt': if tlx is not None: tlx = tlx * 0.3528 if tly is not None: tly = tly * 0.3528 if brx is not None: brx = brx * 0.3528 if bry is not None: bry = bry * 0.3528 elif units == 'px': log.warn("Units set to pixels. Using resolution of %ddpi for area calculations." % res) if tlx is not None: tlx = tlx / res * 25.4 if tly is not None: tly = tly / res * 25.4 if brx is not None: brx = brx / res * 25.4 if bry is not None: bry = bry / res * 25.4 if tlx is not None and brx is not None and tlx >= brx: log.error("Invalid values for tlx (%d) and brx (%d) (tlx>=brx). Using defaults." % (tlx, brx)) tlx = brx = None if tly is not None and bry is not None and tly >= bry: log.error("Invalid values for tly (%d) and bry (%d) (tly>=bry). Using defaults." % (tly, bry)) tly = bry = None # Security: Do *not* create files that other users can muck around with os.umask (0037) if not prop.scan_build: log.error("Scanning disabled in build. Exiting") sys.exit(1) if mode == GUI_MODE: log.error("GUI mode is not implemented yet. Please use -n. Refer to 'hp-scan -h' for help.") sys.exit(1) if not prop.gui_build: log.warn("GUI mode disabled in build. Reverting to interactive mode.") mode = NON_INTERACTIVE_MODE elif not os.getenv('DISPLAY'): log.warn("No display found. Reverting to interactive mode.") mode = NON_INTERACTIVE_MODE elif not utils.checkPyQtImport(): log.warn("PyQt init failed. Reverting to interactive mode.") mode = NON_INTERACTIVE_MODE if mode == GUI_MODE: app = None sendfax = None from qt import * # UI Forms from ui.scanform import ScanForm # create the main application object app = QApplication(sys.argv) if loc is None: loc = user_cfg.ui.get("loc", "system") if loc.lower() == 'system': loc = str(QTextCodec.locale()) log.debug("Using system locale: %s" % loc) if loc.lower() != 'c': e = 'utf8' try: l, x = loc.split('.') loc = '.'.join([l, e]) except ValueError: l = loc loc = '.'.join([loc, e]) log.debug("Trying to load .qm file for %s locale." % loc) trans = QTranslator(None) qm_file = 'hplip_%s.qm' % l log.debug("Name of .qm file: %s" % qm_file) loaded = trans.load(qm_file, prop.localization_dir) if loaded: app.installTranslator(trans) else: loc = 'c' if loc == 'c': log.debug("Using default 'C' locale") else: log.debug("Using locale: %s" % loc) QLocale.setDefault(QLocale(loc)) prop.locale = loc try: locale.setlocale(locale.LC_ALL, locale.normalize(loc)) except locale.Error: pass scanui = ScanForm(device_uri, printer_name, args) app.setMainWidget(scanui) pid = os.getpid() log.debug('pid=%d' % pid) scanui.show() signal.signal(signal.SIGPIPE, signal.SIG_IGN) try: log.debug("Starting GUI loop...") app.exec_loop() except KeyboardInterrupt: pass #except: # log.exception() else: # NON_INTERACTIVE_MODE import Queue from scan import sane import scanext import cStringIO try: import subprocess except ImportError: # Pre-2.4 Python from base import subproc as subprocess try: import Image except ImportError: log.error("'hp-scan -n' requires the Python Imaging Library (PIL). Please install it and try again or run 'hp-scan -u' instead.") sys.exit(1) ## if output_type == 'pdf': ## try: ## from reportlab.pdfgen import canvas ## except ImportError: ## log.error("PDF output requires ReportLab.") ## sys.exit(1) sane.init() devices = sane.getDevices() if not device_uri: if len(devices) == 0: log.error("No scanning devices found.") sys.exit(1) elif len(devices) == 1: device_uri = devices[0][0] else: log.info(log.bold("\nChoose device:\n")) max_deviceid_size = 0 for d in devices: max_deviceid_size = max(len(d[0]), max_deviceid_size) formatter = utils.TextFormatter( ( {'width': 4}, {'width': max_deviceid_size, 'margin': 2}, ) ) log.info(formatter.compose(("Num.", "Device URI"))) log.info(formatter.compose(('-'*4, '-'*(max_deviceid_size)))) i = 0 for d in devices: log.info(formatter.compose((str(i), d[0]))) i += 1 ok, x = tui.enter_range("\nEnter number 0...%d for device (q=quit) ?" % (i-1), 0, (i-1)) if not ok: sys.exit(0) device_uri = devices[x][0] log.info(log.bold("Using device %s" % device_uri)) log.info("Opening connection to device...") try: device = sane.openDevice(device_uri) except scanext.error, e: log.error(e) sys.exit(1) #print device.options #sys.exit(0) tlx = device.getOptionObj('tl-x').limitAndSet(tlx) tly = device.getOptionObj('tl-y').limitAndSet(tly) brx = device.getOptionObj('br-x').limitAndSet(brx) bry = device.getOptionObj('br-y').limitAndSet(bry) scan_area = (brx - tlx) * (bry - tly) # mm^2 scan_px = scan_area * res * res / 645.16 # res is in DPI if scan_mode == 'color': scan_size = scan_px * 3 # 3 bytes/px else: scan_size = scan_px # 1 byte/px if scan_size > 52428800: # 50MB if res > 600: log.warn("Using resolutions greater than 600 dpi will cause very large files to be created.") else: log.warn("The scan current parameters will cause very large files to be created.") log.warn("This can cause the scan to take a long time to complete and may cause your system to slow down.") log.warn("Approx. number of bytes to read from scanner: %s" % utils.format_bytes(scan_size, True)) res = device.getOptionObj('resolution').limitAndSet(res) device.setOption('compression', scanner_compression) if brx - tlx <= 0.0 or bry - tly <= 0.0: log.error("Invalid scan area (width or height is negative).") sys.exit(1) log.info("") log.info("Resolution: %ddpi" % res) log.info("Mode: %s" % scan_mode) log.info("Compression: %s" % scanner_compression) log.info("Scan area (mm):") log.info(" Top left (x,y): (%fmm, %fmm)" % (tlx, tly)) log.info(" Bottom right (x,y): (%fmm, %fmm)" % (brx, bry)) log.info(" Width: %fmm" % (brx - tlx)) log.info(" Height: %fmm" % (bry - tly)) if page_size: units = page_units # for display purposes only log.info("Page size: %s" % size_desc) if units != 'mm': log.note("This scan area below in '%s' units may not be exact due to rounding errors." % units) if units == 'in': log.info("Scan area (in):") log.info(" Top left (x,y): (%fin, %fin)" % (tlx/25.4, tly/25.4)) log.info(" Bottom right (x,y): (%fin, %fin)" % (brx/25.4, bry/25.4)) log.info(" Width: %fin" % ((brx - tlx)/25.4)) log.info(" Height: %fin" % ((bry - tly)/25.4)) elif units == 'cm': log.info("Scan area (cm):") log.info(" Top left (x,y): (%fcm, %fcm)" % (tlx/10.0, tly/10.0)) log.info(" Bottom right (x,y): (%fcm, %fcm)" % (brx/10.0, bry/10.0)) log.info(" Width: %fcm" % ((brx - tlx)/10.0)) log.info(" Height: %fcm" % ((bry - tly)/10.0)) elif units == 'px': log.info("Scan area (px @ %ddpi):" % res) log.info(" Top left (x,y): (%fpx, %fpx)" % (tlx*res/25.4, tly*res/25.4)) log.info(" Bottom right (x,y): (%fpx, %fpx)" % (brx*res/25.4, bry*res/25.4)) log.info(" Width: %fpx" % ((brx - tlx)*res/25.4)) log.info(" Height: %fpx" % ((bry - tly)*res/25.4)) elif units == 'pt': log.info("Scan area (pt):") log.info(" Top left (x,y): (%fpt, %fpt)" % (tlx/0.3528, tly/0.3528)) log.info(" Bottom right (x,y): (%fpt, %fpt)" % (brx/0.3528, bry/0.3528)) log.info(" Width: %fpt" % ((brx - tlx)/0.3528)) log.info(" Height: %fpt" % ((bry - tly)/0.3528)) log.info("Destination(s): %s" % ', '.join(dest)) if 'file' in dest: log.info("Output file: %s" % output) update_queue = Queue.Queue() event_queue = Queue.Queue() device.setOption("mode", scan_mode) device.setOption("resolution", res) if adf: try: device.setOption("source", "ADF") device.setOption("batch-scan", True) except scanext.error: log.error("Failed to set ADF mode. Does this device support ADF? Disabling ADF mode.") adf = False if not adf: try: device.setOption("source", "Auto") device.setOption("batch-scan", False) except scanext.error: log.debug("Error setting source or batch-scan option (this is probably OK).") log.info("\nWarming up...") no_docs = False page = 1 adf_page_files = [] #adf_pages = [] cleanup_spinner() log.info("") try: while True: if adf: log.info("\nPage %d: Scanning..." % page) else: log.info("\nScanning...") bytes_read = 0 try: try: ok, expected_bytes, status = device.startScan("RGBA", update_queue, event_queue) except scanext.error, e: log.error(e) sys.exit(1) except KeyboardInterrupt: log.error("Aborted.") device.cancelScan() sys.exit(1) if adf and status == scanext.SANE_STATUS_NO_DOCS: if page-1 == 0: log.error("No document(s). Please load documents and try again.") sys.exit(0) else: log.info("Out of documents. Scanned %d pages total." % (page-1)) no_docs = True break if adf: log.info("Expecting to read %s from scanner (per page)." % utils.format_bytes(expected_bytes)) else: log.info("Expecting to read %s from scanner." % utils.format_bytes(expected_bytes)) device.waitForScanActive() pm = tui.ProgressMeter("Reading data:") while device.isScanActive(): while update_queue.qsize(): try: status, bytes_read = update_queue.get(0) #if log.get_level() >= log.LOG_LEVEL_INFO: if not log.is_debug(): pm.update(int(100*bytes_read/expected_bytes), utils.format_bytes(bytes_read)) ## if status == scanext.SANE_STATUS_EOF: ## log.debug("EOF") ## elif status == scanext.SANE_STATUS_NO_DOCS: ## log.debug("NO DOCS") ## no_docs = True #elif status != scanext.SANE_STATUS_GOOD: if status != scanext.SANE_STATUS_GOOD: log.error("SANE error %d" % status) sys.exit(1) except Queue.Empty: break time.sleep(0.5) except KeyboardInterrupt: log.error("Aborted.") device.cancelScan() sys.exit(1) # Make sure queue is cleared out... while update_queue.qsize(): status, bytes_read = update_queue.get(0) if not log.is_debug(): pm.update(int(100*bytes_read/expected_bytes), utils.format_bytes(bytes_read)) ## if status == scanext.SANE_STATUS_EOF: ## log.debug("EOF") ## elif status == scanext.SANE_STATUS_NO_DOCS: ## log.debug("NO DOCS") ## no_docs = True if status != scanext.SANE_STATUS_GOOD: log.error("SANE error %d" % status) sys.exit(1) log.info("") if bytes_read: log.info("Read %s from scanner." % utils.format_bytes(bytes_read)) buffer, format, format_name, pixels_per_line, \ lines, depth, bytes_per_line, pad_bytes, total_read = device.getScan() if scan_mode in ('color', 'gray'): try: im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), 'raw', 'RGBA', 0, 1) except ValueError: log.error("Did not read enough data from scanner (I/O Error?)") sys.exit(1) ## elif scan_mode == 'gray': ## im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), ## 'raw', 'RGBA', 0, 1).convert('P') elif scan_mode == 'lineart': try: im = Image.frombuffer('RGBA', (pixels_per_line, lines), buffer.read(), 'raw', 'RGBA', 0, 1).convert('L') except ValueError: log.error("Did not read enough data from scanner (I/O Error?)") sys.exit(1) if adf: temp_output = utils.createSequencedFilename("hpscan_pg%d_" % page, ".png") adf_page_files.append(temp_output) im.save(temp_output) log.debug("Saved page %d to file %s" % (page, temp_output)) #adf_pages.append(im) else: log.error("No data read.") sys.exit(1) if not adf or (adf and no_docs): break page += 1 finally: log.info("Closing device.") device.cancelScan() #print "0" #device.freeScan() #sane.deInit() #if no_docs: # sys.exit(0) #print "1" if adf: try: from reportlab.pdfgen import canvas except ImportError: log.error("PDF output requires ReportLab.") sys.exit(1) #print "2" #print canvas #print canvas.Canvas tlx_max = device.getOptionObj('tl-x').constraint[1] bry_max = device.getOptionObj('br-y').constraint[1] if not output: output = utils.createSequencedFilename("hpscan", ".pdf") c = canvas.Canvas(output, (tlx_max/0.3528, bry_max/0.3528)) #print c #for p in adf_page_files: for p in adf_page_files: log.info("Processing page %s..." % p) image = Image.open(p) #print image try: c.drawInlineImage(image, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)), #c.drawInlineImage(image, 0, bry/0.3528, width=None,height=None) except NameError: log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.") sys.exit(1) c.showPage() log.info("Saving to file %s" % output) c.save() log.info("Viewing PDF file in %s" % pdf_viewer) os.system("%s %s &" % (pdf_viewer, output)) sys.exit(0) if resize != 100: if resize < 1 or resize > 400: log.error("Resize parameter is incorrect. Resize must be 0% < resize < 400%.") log.error("Using resize value of 100%.") else: new_w = pixels_per_line * resize / 100 new_h = lines * resize / 100 log.info("Resizing from %dx%d to %dx%d..." % (pixels_per_line, lines, new_w, new_h)) im = im.resize((new_w, new_h), Image.ANTIALIAS) file_saved = False if 'file' in dest: log.info("\nOutputting to destination 'file':") log.info("Saving to file %s" % output) try: im.save(output) except IOError, e: log.error("Error saving file: %s (I/O)" % e) try: os.remove(output) except OSError: pass sys.exit(1) except ValueError, e: log.error("Error saving file: %s (PIL)" % e) try: os.remove(output) except OSError: pass sys.exit(1) file_saved = True dest.remove("file") temp_saved = False if ('editor' in dest or 'viewer' in dest or 'email' in dest or 'printer' in dest) \ and not file_saved: output_fd, output = utils.make_temp_file(suffix='.png') try: im.save(output) except IOError, e: log.error("Error saving temporary file: %s" % e) try: os.remove(output) except OSError: pass sys.exit(1) os.close(output_fd) temp_saved = True for d in dest: log.info("\nSending to destination '%s':" % d) if d == 'fax': log.error("fax: Not implemented yet.") elif d == 'pdf': try: from reportlab.pdfgen import canvas except ImportError: log.error("PDF output requires ReportLab.") continue tlx_max = device.getOptionObj('tl-x').constraint[1] bry_max = device.getOptionObj('br-y').constraint[1] pdf_output = utils.createSequencedFilename("hpscan", ".pdf") c = canvas.Canvas(pdf_output, (tlx_max/0.3528, bry_max/0.3528)) try: c.drawInlineImage(im, (tlx/0.3528), ((bry_max/0.3528)-(bry/0.3528)), width=None,height=None) except NameError: log.error("A problem has occurred with PDF generation. This is a known bug in ReportLab. Please update your install of ReportLab to version 2.0 or greater.") continue c.showPage() log.info("Saving to file %s" % pdf_output) c.save() log.info("Viewing PDF file in %s" % pdf_viewer) os.system("%s %s &" % (pdf_viewer, pdf_output)) elif d == 'printer': hp_print = utils.which("hp-print") if hp_print: cmd = 'hp-print %s &' % output else: cmd = "python ./print.py %s &" % output os.system(cmd) elif d == 'email': try: from email.mime.image import MIMEImage from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText except ImportError: try: from email.MIMEImage import MIMEImage from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText except ImportError: log.error("hp-scan email destination requires Python 2.2+.") continue msg = MIMEMultipart() msg['Subject'] = email_subject msg['From'] = email_from msg['To'] = ','.join(email_to) msg.preamble = 'Scanned using hp-scan' if email_note: txt = MIMEText(email_note) msg.attach(txt) if file_saved: txt = MIMEText("attached: %s: %dx%d %s PNG image." % (os.path.basename(output), pixels_per_line, lines, scan_mode)) else: txt = MIMEText("attached: %dx%d %s PNG image." % (pixels_per_line, lines, scan_mode)) msg.attach(txt) fp = open(output, 'r') img = MIMEImage(fp.read()) fp.close() if file_saved: img.add_header('Content-Disposition', 'attachment', filename=os.path.basename(output)) msg.attach(img) sendmail = utils.which("sendmail") if sendmail: sendmail = os.path.join(sendmail, 'sendmail') cmd = [sendmail,'-t','-r',email_from] log.debug(repr(cmd)) err = None try: sp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) std_out, std_err = sp.communicate(msg.as_string()) if std_err != '': err = std_err except OSError, e: err = str(e) cleanup_spinner() if err: log.error(repr(err)) else: log.error("Mail send failed. 'sendmail' not found.") elif d == 'viewer': if viewer: log.info("Viewing file in %s" % viewer) os.system("%s %s &" % (viewer, output)) else: log.error("Viewer not found.") elif d == 'editor': if editor: log.info("Editing file in %s" % editor) os.system("%s %s &" % (editor, output)) else: log.error("Editor not found.") device.freeScan() sane.deInit() except KeyboardInterrupt: log.error("User exit") log.info("") log.info("Done.")